package math;

import java.util.Random;

public class Maths {

    private static final Random gen = new Random();
    public static final float PI = (float) Math.PI;
    public static final float TWOPI = (float) (2 * Math.PI);
    public static final float PID2 = (float) (Math.PI / 2d);
    public static final float RT2D2 = (float) (Math.sqrt(2d) / 2d);

    private Maths() {
    }
    
    public static final float noise(int x) {
        x = (x << 13) ^ x;
        x = (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff;
        return 1.0f - x / 1073741824.0f;
    }

    public static final float noiseSmooth(int x) {
        return noise(x) / 2.0f + noise(x - 1) / 4.0f + noise(x + 1) / 4.0f;
    }
    
//    public static float lerpPerspec(float t, float y0, float y1, float z0, float z1) {
//        float s = t * z0 / (t * z0 + (1 - t) * z1);
//        return lerp(s, y0, y1);
//    }
    
    public static float lerpPerspec(float x, float x0, float x1, float z0, float z1, float p0, float p1) {
        float t = (x - x0) / (x1 - x0);
        float s = t * z0 / (t * z0 + (1 - t) * z1);
        return lerp(s, p0, p1);
    }
    
    public static float lerp(float t, float y0, float y1) {
        return (1 - t) * y0 + t * y1;
    }
    
    public static float lerpf(float x, float x0, float x1, float y0, float y1) {
        float dydx = (y1 - y0) / (x1 - x0);
        return (y0 + (x - x0) * dydx);
    }

    public static int lerpi(float x, float x0, float x1, float y0, float y1) {
        float dydx = (y1 - y0) / (x1 - x0);
        return (int)(y0 + (x - x0) * dydx);
    }
    
    public static int lcm(int a, int b, int c) {
        int t;
        if (a < b) { // Now a >= b
            t = b;  b = a;  a = t;
        }
        if (a < c) { // Now a >= b and a >= c
            t = c;  c = a;  a = t;
        }
        if (b < c) { // Now a >= b >= c
            t = c;  c = b;  b = t;
        }
        int multiple = 1;
        int remb, remc;
        int multa = (multiple * a);
        remb = multa % b;
        remc = multa % c;
        while (remb != 0 || remc != 0) {
            multiple++;
            multa = (multiple * a);
            remb = multa % b;
            remc = multa % c;
        }
        return multa;
    }
    
    public static float mod(float num, float denom) {
       if (num < 0) {
           return (num % denom) + denom;
       } else {
           return (num % denom);
       }
    }

    public static int randomInt(int limit) {
        return gen.nextInt(limit);
    }

    public static int nearestInt(float num) {
        if (num >= 0) {
            return (int) (num + 0.5f);
        } else {
            return (int) (num - 0.5f);
        }
    }

    public static int floor(float num) {
        if (num >= 0) {
            return (int) num;
        } else {
            return (int) (num - 1);
        }
    }
    
    public static int ceil(float num) {
        return floor(num) + 1;
    }

    public static float log(float base, float num) {
        return (ln(num) / ln(base));
    }

    public static float log10(float num) {
        return (float) Math.log10(num);
    }

    public static float ln(float num) {
        return (float) Math.log(num);
    }

    public static float cap(float num, float low, float high) {
        float n = num;
        if (n < low) {
            n = low;
        } else if (n > high) {
            n = high;
        }
        return n;
    }

    public static int capi(int num, int low, int high) {
        int n = num;
        if (n < low) {
            n = low;
        } else if (n > high) {
            n = high;
        }
        return n;
    }

    public static float pow(float base, float exp) {
        return (float) Math.pow(base, exp);
    }

    public static float powIter(float base, float exp) {
        float total = base;
        for (int i = 1; i < exp; i++) {
            total *= base;
        }
        return total;
    }

    public static float random() {
        float randomUnit = randomUnit();
        if (randomUnit > 0.5) {
            return randomUnit() * Float.MAX_VALUE;
        } else {
            return -randomUnit() * Float.MAX_VALUE;
        }
    }

    public static float randomPos() {
        float temp = random();
        if (temp < 0f) {
            temp *= -1f;
        }
        return temp;
    }

    public static float randomUnit() {
        return (float) (gen.nextDouble());
    }

    public static float randomNegToPosOne() {
        return (float) (gen.nextDouble() * 2 - 1);
    }

    public static int min(int a, int b) {
        return Math.min(a, b);
    }

    public static int max(int a, int b) {
        return Math.max(a, b);
    }

    public static int min(int a, int b, int c) {
        return min(min(a, b), c);
    }

    public static int max(int a, int b, int c) {
        return max(max(a, b), c);
    }

    public static int min(int a, int b, int c, int d) {
        return min(min(a, b), min(c, d));
    }

    public static int max(int a, int b, int c, int d) {
        return max(max(a, b), max(c, d));
    }

    public static float min(float a, float b) {
        return Math.min(a, b);
    }

    public static float max(float a, float b) {
        return Math.max(a, b);
    }

    public static float min(float a, float b, float c) {
        return min(min(a, b), c);
    }

    public static float max(float a, float b, float c) {
        return max(max(a, b), c);
    }

    public static float min(float a, float b, float c, float d) {
        return min(min(a, b), min(c, d));
    }

    public static float max(float a, float b, float c, float d) {
        return max(max(a, b), max(c, d));
    }

    public static float cosRad(float t) {
        return (float) Math.cos(t);
    }

    public static float cosDeg(float t) {
        return (float) Math.cos(t * PI / 180f);
    }

    public static float sinRad(float t) {
        return (float) Math.sin(t);
    }

    public static float sinDeg(float t) {
        return (float) Math.sin(t * PI / 180f);
    }

    public static float tanRad(float t) {
        return (float) Math.tan(t);
    }

    public static float tanDeg(float t) {
        return (float) Math.tan(t * PI / 180f);
    }

    public static float atanRad(float y, float x) {
        return (float) Math.atan2(y, x);
    }

    public static float atanDeg(float y, float x) {
        return atanRad(y, x) * 180f / PI;
    }

    public static float acosRad(float t) {
        return (float) Math.acos(t);
    }

    public static float acosDeg(float t) {
        return acosRad(t) * 180f / PI;
    }

    public static float asinRad(float t) {
        return (float) Math.asin(t);
    }

    public static float asinDeg(float t) {
        return asinRad(t) * 180f / PI;
    }

    public static float sq(float base) {
        return pow(base, 2);
    }

    public static float sqrt(float base) {
        return (float) Math.sqrt(base);
    }

    public static float sqrt(double base) {
        return (float) Math.sqrt(base);
    }

    public static float abs(float num) {
        if (num < 0) {
            return -num;
        }
        return num;
    }

    public static int abs(int num) {
        if (num < 0) {
            return -num;
        }
        return num;
    }

    public static float toRadians(float num) {
        return (float) Math.toRadians(num);
    }

    public static float toDegrees(float num) {
        return (float) Math.toDegrees(num);
    }

    public static float[][] multMatrix(float[][] a, float[][] b) {
        if (a[0].length != b.length) {
            return null;
        }
        int rowResult = a.length;
        int colResult = b[0].length;
        float[][] temp = new float[rowResult][colResult];
        for (int i = 0; i < temp.length; i++) {
            for (int j = 0; j < temp[0].length; j++) {
                float sum = getDotProduct(a, b, i, j);
                temp[i][j] = sum;
            }
        }
        return temp;
    }
    
    public static float[] multMat4(float[] a, float[] b) {
        if (a.length != b.length) {
            return null;
        }
        float[] result = new float[a.length];
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                result[i + 4 * j] = getDotProduct4(a, b, i, j);
            }
        }
        return result;
    }
    
    public static float[] multMat3(float[] a, float[] b) {
        if (a.length != b.length) {
            return null;
        }
        float[] result = new float[a.length];
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                result[i + 3 * j] = getDotProduct3(a, b, i, j);
            }
        }
        return result;
    }
    
    public static float[] multMatN(int nComponents, float[] a, float[] b) {
        if (a.length != b.length) {
            return null;
        }
        float[] result = new float[a.length];
        for (int i = 0; i < nComponents; i++) {
            for (int j = 0; j < nComponents; j++) {
                result[i + nComponents * j] = getDotProductN(nComponents, a, b, i, j);
            }
        }
        return result;
    }
    
    private static float getDotProduct4(float[] a, float[] b, int rowIndex, int colIndex) {
        float sum = 0;
        for (int i = 0; i < 4; i++) {
            sum += (a[rowIndex + 4 * i] * b[i + 4 * colIndex]);
        }
        return sum;
    }
    
    private static float getDotProduct3(float[] a, float[] b, int rowIndex, int colIndex) {
        float sum = 0;
        for (int i = 0; i < 3; i++) {
            sum += (a[rowIndex + 3 * i] * b[i + 3 * colIndex]);
        }
        return sum;
    }
    
    private static float getDotProductN(int nComponents, float[] a, float[] b, int rowIndex, int colIndex) {
        float sum = 0;
        for (int i = 0; i < nComponents; i++) {
            sum += (a[rowIndex + nComponents * i] * b[i + nComponents * colIndex]);
        }
        return sum;
    }

    private static float getDotProduct(float[][] a, float[][] b, int rowIndex, int colIndex) {
        float sum = 0;
        for (int i = 0; i < a[0].length; i++) {
            sum += (a[rowIndex][i] * b[i][colIndex]);
        }
        return sum;
    }

    public static boolean bBox2DContainsExclusive(float l, float r, float b, float t, float xpos, float ypos) {
        if (xpos <= l || xpos >= r) {
            return false;
        }
        if (ypos <= b || ypos >= t) {
            return false;
        }
        return true;
    }

    public static boolean bBox2DContainsExclusivec(float cx, float cy, float wt, float ht, float xpos, float ypos) {
        return bBox2DContainsExclusive(cx - wt / 2, cx + wt / 2, cy - ht / 2, cy + ht / 2, xpos, ypos);
    }

}
